/**
 *
 */
package idao.engine.executor;

import idao.DaoRuntimeException;
import idao.TooManyRecordsException;
import idao.NoRecordFoundException;
import idao.Query;
import idao.ResultSetHandler;
import idao.Reusable;
import idao.RowHandler;
import idao.engine.DaoUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Default built in {@link ResultSetHandler}.
 * This class is thread safe after instantiated, and can be reused.
 * 
 * @author huhao
 */
@Reusable
class DefaultResultSetHandler implements ResultSetHandler
{
    // return type of the dao method
    private Class<?> returnType;
    // row processor used to get data from result set
    private RowHandler rowHandler;
    // row handler metadata for custom row handler implementation
    private Query.RowHandler rowHandlerMetadata;

    private boolean expectingMultipleRow;

    // no intention to be used externally, except subclass.
    protected DefaultResultSetHandler(Method m)
    {
        returnType = m.getReturnType();
        expectingMultipleRow = Collection.class.isAssignableFrom(returnType);

        rowHandlerMetadata = m.getAnnotation(Query.RowHandler.class);
        if (rowHandlerMetadata != null) {
            if (rowHandlerMetadata.value().isAnnotationPresent(Reusable.class)) {
                rowHandler = this.getRowHandler();
            }
        } else {
            Class<?> requiredType = null;
            if (expectingMultipleRow) {// get element type for collection
                Type grt = m.getGenericReturnType();
                if (grt instanceof ParameterizedType) {
                    ParameterizedType paramType = (ParameterizedType)grt;
                    Type[] actualTypeArgs = paramType.getActualTypeArguments();
                    if ((actualTypeArgs.length == 1) && (actualTypeArgs[0] instanceof Class<?>)) {
                        requiredType = (Class<?>)actualTypeArgs[0];
                    }
                }
                if ((requiredType == null) || requiredType.isInterface()
                                || Modifier.isAbstract(requiredType.getModifiers())) {
                    throw new DaoRuntimeException("Element type must be a concrete class");
                }
            } else {
                requiredType = returnType;
            }
            rowHandler = new DefaultRowHandler(requiredType);
        }
    }

    protected Class<?> getReturnType()
    {
        return returnType;
    }

    public Object handleResultSet(ResultSet rs) throws SQLException
    {
        if (expectingMultipleRow) {
            return this.extractCollection(rs);
        }
        return this.extractSingleRow(rs);
    }

    private Object extractSingleRow(ResultSet rs) throws SQLException
    {
        if (!rs.next()) {
            // no result
            throw new NoRecordFoundException("Expecting exactly 1 record, but got 0!");
        }
        // get value
        Object value = this.getRowHandler().handleRow(rs, 1);
        if (rs.next()) {
            // more than one row
            throw new TooManyRecordsException("Expecting exactly 1 record, but got too many!");
        }
        return value;
    }

    private Collection<Object> extractCollection(ResultSet rs) throws SQLException
    {
        // initialize container
        Collection<Object> c;
        if (List.class.equals(this.getReturnType())
                        || Collection.class.equals(this.getReturnType())) {
            c = new ArrayList<Object>();
        } else if (Set.class.equals(this.getReturnType())) {
            c = new HashSet<Object>();
        } else {
            throw new DaoRuntimeException("Unsupported collection type [" + getReturnType() + "]");
        }
        // extract data
        int i = 1;
        while (rs.next()) {
            c.add(this.getRowHandler().handleRow(rs, i++));
        }
        return c;
    }

    private RowHandler getRowHandler()
    {
        return rowHandler != null ? rowHandler : 
            DaoUtils.newInstance(rowHandlerMetadata.value(), rowHandlerMetadata.initParameters());
    }
}
